Skip to content

9.5 仓储模式

📝 模块更新日志 新特性*

+ 新增 `IRepositoryFactory<TEntity, TDbContextLocator>` 仓储功能,解决在 `Blazor` 中使用 `EFCore` 问题 4\.9\.1\.1 ⏱️2023\.11\.16 [4285ec0](https://gitee.com/dotnetchina/Furion/commit/4285ec0b8debc2d71c7f978126cb3dc394a8ad30) [文档说明](https://learn.microsoft.com/zh-cn/aspnet/core/blazor/blazor-ef-core?view=aspnetcore-7.0)

9.5.1 什么是仓储

在领域层和数据映射层的中介,使用类似集合的接口来存取领域对象,实际上,仓储被用于领域对象在数据库上的操作(实体 Entity 和值对象 Value types)。一般来说,我们针对不同的实体(或聚合根 Aggregate Root)会创建相对应的仓储。

简单来说,仓储就是数据存取操作的载体,但不限定于数据库。

9.5.2 内置仓储

Furion 框架内置了一个数据库操作的仓储,方便大家拓展和集成:

关于依赖注入说明目前能够被依赖注入解析服务的仓储有:

  • IRepository
  • IRepository<TEntity>
  • IRepository<TEntity, TDbContextLocator>
  • ISqlRepository
  • ISqlRepository<TDbContextLocator>
  • IMSRepository
  • IMSRepository<TMasterDbContextLocator>
  • IMSRepository<TMasterDbContextLocator,...TSlaveDbContextLocator8>
  • IDbRepository<TDbContextLocator>

还有两个私有仓储,也是所有仓储的基类(用于高级自定义开发)

  • IPrivateRepository<TEntity>:所有实体仓储的基类
  • IPrivateSqlRepository:所有数据库操作的基类

除此之后的所有仓储只能通过 rep.Constraint<TRepository>() 进行约束创建,如,只读仓储:

var readRepository = rep.Constraint<IReadableRepository<TEntity>>();  

9.5.2.1 非泛型超级仓储

  • IRepository:默认非泛型仓储接口,支持切换到任何仓储
  • EFCoreRepository:默认非泛型仓储实现

9.5.2.2 泛型实体仓储

  • IRepository<TEntity>:默认数据库实体仓储接口
  • EFCoreRepository<TEntity>:默认数据库实体仓储实现

9.5.2.3 泛型多数据库实体仓储

  • IRepository<TEntity, TDbContextLocator>:任意数据库的实体仓储接口
  • EFCoreRepository<TEntity, TDbContextLocator>:任意数据库的实体仓储实现

9.5.2.4 Sql 操作仓储

  • ISqlRepository:默认数据库 Sql 操作仓储接口
  • SqlRepository:默认数据库 Sql 操作仓储实现

9.5.2.5 多数据库 Sql 操作仓储

  • ISqlRepository<TDbContextLocator>:任意数据库的 Sql 操作仓储接口
  • SqlRepository<TDbContextLocator>:任意数据库的 Sql 操作仓储实现

9.5.2.6 只读实体仓储(支持多库)

  • IReadableRepository<TEntity>:默认数据库只读实体仓储接口
  • IReadableRepository<TEntity, TDbContextLocator>:多数据库只读实体仓储实现

9.5.2.7 只写实体仓储(支持多库)

  • IWritableRepository<TEntity>:默认数据库只写实体仓储接口
  • IWritableRepository<TEntity, TDbContextLocator>:多数据库只写实体仓储实现

9.5.2.8 只允许新增实体仓储(支持多库)

  • IInsertableRepository<TEntity>:默认数据库只允许新增的实体仓储接口
  • IInsertableRepository<TEntity, TDbContextLocator>:多数据库只允许新增的实体仓储实现

9.5.2.9 只允许更新实体仓储(支持多库)

  • IUpdateableRepository<TEntity>:默认数据库只允许更新的实体仓储接口
  • IUpdateableRepository<TEntity, TDbContextLocator>:多数据库只允许更新的实体仓储实现

9.5.2.10 只允许删除实体仓储(支持多库)

  • IDeletableRepository<TEntity>:默认数据库只允许删除的实体仓储接口
  • IDeletableRepository<TEntity, TDbContextLocator>:多数据库只允许删除的实体仓储实现

9.5.2.11 只允许拓展操作实体仓储(支持多库)

功能移除声明该功能在 Furion 2.5.1 + 版本中已移除。此操作让很多不了解 EFCore 的开发者产生了很大的误解,不知何时新增或何时更新,故移除此功能。

  • IOperableRepository<TEntity>:默认数据库只允许拓展操作实体仓储接口
  • IOperableRepository<TEntity, TDbContextLocator>:多数据库只允许拓展操作实体仓储实现

9.5.2.12 只允许 Sql 查询仓储(支持多库)

  • ISqlReaderRepository:默认数据库只允许 Sql 查询仓储接口
  • ISqlReaderRepository<TDbContextLocator>:多数据库只允许 Sql 查询仓储实现

9.5.2.13 只允许 Sql 非查询仓储(支持多库)

  • ISqlExecutableRepository:默认数据库只允许 Sql 非查询仓储接口
  • ISqlExecutableRepository<TDbContextLocator>:多数据库只允许 Sql 非查询仓储实现

9.5.2.14 读写分离仓储

  • IMSRepository:最多支持 一主 7 从 仓储

9.5.2.15 定位器仓储

  • IDbRepository<TDbContextLocator>:初始化特定数据库仓储

9.5.2.15 仓储工厂

  • IRepositoryFactory<TEntity>:创建新的未释放的 IRepository<TEntity> 仓储,Furion 4.9.1.1+ 支持
  • IRepositoryFactory<TEntity, TDbContextLocator>:创建新的未释放的 IRepository<TEntity, TDbContextLocator> 仓储,Furion 4.9.1.1+ 支持

9.5.3 仓储使用

Furion 提供了非常多的方式创建仓储,目的是为了让大家可以在不同的场景中使用。

9.5.3.1 构造函数注入

private readonly IRepository<Person> _personRepository;  
public FurionService(IRepository<Person> personRepository)  
{  
    _personRepository = personRepository;  
}  

9.5.3.2 方法参数注入

public async Task<List<PersonDto>> GetAll([FromServices] IRepository<Person> repository, string keyword)  
{  
    var persons = await repository.AsQueryable().ToListAsync();  
    return persons.Adapt<List<PersonDto>>();  
}  

9.5.3.3 Db.GetRepository 获取

// 非泛型仓储  
var repository = Db.GetRepository();  

// 泛型仓储  
var repository = Db.GetRepository<Person>();  

// Sql 仓储  
var sqlRepository = Db.GetSqlRepository();  

特别说明不管采用哪种方式,Furion 都保证了仓储一次请求唯一性。同时 Db.GetRepository<TEntity>() 方式支持任何静态类中使用。

9.5.4 仓储高级用法

9.5.4.1 动态切换实体仓储

var userRepository = personRepository.Change<User>();  
var userRepository = personRepository.Change<User, MasterDbContextLocator>();   // 还可以基于定位器切换  

9.5.4.2 动态切换仓储类型

比如,读写分离/主从库仓储:

// 只读仓储  
var readRepository = personRepository.Constraint<IReadableRepository<User>>();  

// 只写仓储  
var writeRepository = personRepository.Constraint<IWritableRepository<User>>();  

小知识.Constraint 支持切换任何仓储类型。

9.5.4.3 获取 Sql 操作仓储

var sqlRepository = repository.Sql();  

9.5.5 多数据库操作

Furion 通过 DbContextLocator 数据库上下文定位器实现多种数据库操作,可以随意切换数据库

9.5.5.1 动态切换多个数据库

动态切换数据库

// 切换到 MSSQL 操作 Person表  
var mssqlRepository = repository.Change<Person, MsSqlDbContextLocator>();  

// 切换到 MySql 操作 Person表  
var mysqlRepository = repository.Change<Person, MySqlDbContextLocator>();  

// 切换到 Sqlite 操作 Person表  
var sqliteRepository = repository.Change<Person, SqliteDbContextLocator>();  

// 其他更多数据库一样的操作  

另外任何仓储或实体配置都支持多个数据库同时操作

仓储方式

IRepository<Person, MsSqlDbContextLocator> mssqlRepository  

ISqlRepository<MsSqlDbContextLocator> mssqlRepository;  

动态 sql 方式

"select * from person".Change<MsSqlDbContextLocator>().SqlQuery();  

实体配置方式

public class User:Entity<int, MsSqlDbContextLocator, MySqlDbContextLocator>  
{  
}  

Sql 代理方式

[SqlFunction("funcName", DbContextLocator = typeof(MySqlDbContextLocator))]  
int GetAge(int id);  

Linq 中方式

[QueryableFunction("funcName","dbo", DbContextLocator = typeof(MySqlDbContextLocator))]  
string GetName()=> throw Oops.Oh("不支持该数据库操作");  

9.5.6 在后台任务中使用

由于 DbContext 默认注册为 Scoped 生存周期,所以在后台任务中使用 IServiceScopeFactory 获取所有服务,如:

public class JobService : BackgroundService  
{  
    // 日志对象  
    private readonly ILogger<JobService> _logger;  

    // 服务工厂  
    private readonly IServiceScopeFactory _scopeFactory;  
    public JobService(ILogger<JobService> logger  
        , IServiceScopeFactory scopeFactory)  
    {  
        _logger = logger;  
        _scopeFactory = scopeFactory;  
    }  

    protected override Task ExecuteAsync(CancellationToken stoppingToken)  
    {  
        _logger.LogInformation("写日志~~");  

        using (var scope = _scopeFactory.CreateScope())  
        {  
            var services = scope.ServiceProvider;  

            // 获取数据库上下文  
            var dbContext = Db.GetDbContext(services);  

            // 获取仓储  
            var respository = Db.GetRepository<Person>(services);  

            // 解析其他服务  
            var otherService = services.GetService<XXX>();  
        }  

        return Task.CompletedTask;  
    }  
}  

数据库操作注意如果作用域中对数据库有任何变更操作,需手动调用 SaveChanges 或带 Now 结尾的方法。也可以使用 Scoped.CreateUow(handler) 代替。

9.5.7 仓储拓展

很多时候,我们无需自定义仓储,而是通过仓储拓展类(拓展方法)的方式实现更多功能,如:

public static class MyRepositoryExtensions  
{  
    /// <summary>  
    /// 软删除  
    /// </summary>  
    /// <typeparam name="TEntity"></typeparam>  
    /// <param name="repository"></param>  
    public static void FakeDelete<TEntity>(this IPrivateRepository<TEntity> repository)  
        where TEntity : class, IPrivateEntity, new()  
    {  
        // 您的代码  
    }  


    /// <summary>  
    /// 其他方法  
    /// </summary>  
    /// <typeparam name="TEntity"></typeparam>  
    /// <param name="repository"></param>  
    public static void OtherMethod<TEntity>(this IPrivateRepository<TEntity> repository)  
        where TEntity : class, IPrivateEntity, new()  
    {  
        // 您的代码  
    }  
}  

这样就可以通过以下代码使用:

repository.FakeDelete();  

repository.OtherMethod();  

9.5.8 自定义仓储

有些时候我们需要自定义仓储,拓展现有的仓储功能,可参考以下代码(含定位器仓储和默认仓储实现)

/// <summary>  
/// 自定义仓储接口  
/// </summary>  
/// <typeparam name="TEntity"></typeparam>  
/// <typeparam name="TDbContextLocator"></typeparam>  
public interface IMyRepository<TEntity, TDbContextLocator> : IPrivateRepository<TEntity>  
    where TEntity : class, IPrivateEntity, new()  
    where TDbContextLocator : class, IDbContextLocator  
{  
    /// <summary>  
    /// 自定义方法  
    /// </summary>  
    void MyMethod();  
}  

/// <summary>  
/// 自定义仓储实现类  
/// </summary>  
/// <typeparam name="TEntity"></typeparam>  
/// <typeparam name="TDbContextLocator"></typeparam>  
public class MyRepository<TEntity, TDbContextLocator> : PrivateRepository<TEntity>, IMyRepository<TEntity, TDbContextLocator>, IScoped  
    where TEntity : class, IPrivateEntity, new()  
    where TDbContextLocator : class, IDbContextLocator  
{  
    /// <summary>  
    /// 实现基类构造函数  
    /// </summary>  
    /// <param name="serviceProvider"></param>  
    public MyRepository(IServiceProvider serviceProvider)  
        : base(typeof(TDbContextLocator), serviceProvider)  
    {  
    }  

    /// <summary>  
    /// 自定义方法  
    /// </summary>  
    public void MyMethod()  
    {  
        throw new System.NotImplementedException();  
    }  
}  

/// <summary>  
/// 默认数据库自定义仓储接口  
/// </summary>  
/// <typeparam name="TEntity"></typeparam>  
public interface IMyRepository<TEntity> : IMyRepository<TEntity, MasterDbContextLocator>  
    where TEntity : class, IPrivateEntity, new()  
{  
}  

/// <summary>  
/// 默认数据库自定义仓储实现  
/// </summary>  
/// <typeparam name="TEntity"></typeparam>  
public class MyRepository<TEntity> : MyRepository<TEntity, MasterDbContextLocator>, IMyRepository<TEntity>, IScoped  
    where TEntity : class, IPrivateEntity, new()  
{  
    public MyRepository(IServiceProvider serviceProvider) : base(serviceProvider)  
    {  
    }  
}  

9.5.9 仓储工厂 IRepositoryFactory<TEntity>

版本说明以下内容仅限 Furion 4.9.1.1 + 版本使用。

有时候我们希望可以通过类型 new 的方式创建一个 IRepository<TEntity> 实例,然后通过 using 实现手动释放,如:

public class YourAppServices : IDynamicApiController  
{  
    private readonly IRepositoryFactory<Person> _repositoryFactory;  

    public YourAppServices(IRepositoryFactory<Person> repositoryFactory)  
    {  
        _repositoryFactory = repositoryFactory;  
    }  

    public void Create()  
    {  
        using var repository = _repositoryFactory.CreateRepository();  
        var persons = repository.DetachedEntities.ToList();  

        using (var repository2 = _repositoryFactory.CreateRepository())  
        {  
            var persons2 = repository.DetachedEntities.ToList();  
        }  

        var persons3 = repository.DetachedEntities.ToList();  
    }  
}  

这样的功能非常适用于多线程或者 Blazor 项目中(相关文档),如:

@inject IRepositoryFactory<Person> Factory; // 可以注入多个  

private async Task GetListAsync()  
{  
    // 别忘记 using  
    using var repository = Factory.CreateRepository();  // 也可以通过 using var dbContext = Db.CreateDbContext() 获取新的 DbContext  

    var list = await respository.DetachedEntities.ToListAsync();  
}  

9.5.10 反馈与建议

与我们交流给 Furion 提 Issue